#!/usr/bin/perl -w
use strict;

my %G_opts = ( sortby => "name", verbose => 0 );

my @opts = @ARGV; @ARGV = ();
while (@opts) {
  my $opt = shift @opts;
  if( $opt !~ /^-/ ) { push( @ARGV, $opt ); }
  elsif( $opt eq "-v" ) { $G_opts{verbose} = 1; }
  elsif( $opt eq "-n" ) { $G_opts{sortby} = "name"; }
  elsif( $opt eq "-s" ) { $G_opts{sortby} = "size"; }
  else { die "Unknown command line opion $opt\n"; }
}

my $SortKeys = $G_opts{sortby} eq "name" ? \&keysbyname : \&keysbysize;

if( @ARGV < 1 ) {
  print "usage: module_ram_usage (-v|-s|-n) [exe] (module) (module) (...)\n";
  exit 0;
}

my $exe = shift @ARGV;
my %modules = map { $_ => 1 } @ARGV;

my %segs = ();

my $cmd = "avr-objdump -x \"$exe\"";
open( FH, "$cmd |" ) or die "ERROR, $cmd failed: $!\n";

while( <FH> ) {
  if( /^\S+\s+\w+\s+\w+\s+\.(bss|data|text)\s+(\S+)\s+(\S+)/ ) {
    my ($segment,$size,$name) = ($1,$2,$3);
    (my $module = $name) =~ s/\$.*//;
    $segs{$segment} = { total => {}, vars => {} } unless exists $segs{$segment};
    my $seg = $segs{$segment};
    $seg->{total}->{$module} += hex $size;
    if( $name =~ /\$(.*)/ ) {
      $seg->{vars}->{$module} = {} unless exists $seg->{vars}->{$module};
      $seg->{vars}->{$module}->{$1} += hex $size;
    }
  }
}

close( FH );

my $n = 0;
my %titles = ( bss => "Heap RAM", text => "Program ROM", data => "Constants ROM" );
for my $seg (qw(text data bss)) {

  my $total = $segs{$seg}->{total};
  my $vars = $segs{$seg}->{vars};

  my $sum = 0;
  for my $v (values %$total) { $sum += $v; }

  print "\n" if $n++;

  print "$sum bytes of $titles{$seg} in $exe, usage by "
      . (%modules?"selected ":"")
      . "module:\n";

  for my $module (&$SortKeys($total)) {
    next if %modules && not $modules{$module};
    printf( "%8d  %s\n", $total->{$module}, $module ) if $total->{$module};
    if( $G_opts{verbose} ) {
      for my $var (&$SortKeys($vars->{$module})) {
	printf( "%8s >%6d  %s\n", "", $vars->{$module}->{$var}, $var );
      }
    }
  }
}


sub keysbyname {
  my $h = shift;
  return sort keys %$h;
}

sub keysbysize {
  my $h = shift;
  return sort {$h->{$a} <=> $h->{$b} || $a cmp $b} keys %$h;
}
